package cn.mazexal.examapi.common.config;

import cn.mazexal.examapi.common.models.BizException;
import cn.mazexal.examapi.common.models.EmptyResponse;
import cn.mazexal.examapi.common.models.Response;
import cn.mazexal.examapi.common.models.UnauthorizedException;
import cn.mazexal.examapi.common.models.exception.ForbiddenException;
import cn.mazexal.examapi.common.models.exception.NotFoundException;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import java.util.*;

/**
 * @author: Mazexal
 * Date: 2025/6/4
 */
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends BasicErrorController {

    private static final Map<HttpStatus, Response> responseMap = new HashMap<>();

    static {
        responseMap.put(HttpStatus.UNAUTHORIZED, EmptyResponse.error("Unauthorized", "请先登录"));
        responseMap.put(HttpStatus.FORBIDDEN, EmptyResponse.error("Forbidden", "没有权限"));
        responseMap.put(HttpStatus.NOT_FOUND, EmptyResponse.error("NotFound", "资源不存在"));
        responseMap.put(HttpStatus.INTERNAL_SERVER_ERROR, EmptyResponse.error("InternalServerError", "服务器走神了，请稍后再试"));
        responseMap.put(HttpStatus.BAD_REQUEST, EmptyResponse.error("BadRequest", "请求参数异常"));
        responseMap.put(HttpStatus.METHOD_NOT_ALLOWED, EmptyResponse.error("MethodNotAllowed", "不支持该请求方式"));
    }

    @Resource
    private DefaultErrorAttributes errorAttributes;

    public GlobalExceptionHandler(ServerProperties serverProperties) {
        super(new DefaultErrorAttributes(), serverProperties.getError());
    }

    /**
     * 处理错误请求的函数
     *
     * @param request 包含请求信息的对象
     * @return 包含响应信息的ResponseEntity对象
     */
    @RequestMapping
    @ResponseBody
    @Operation(hidden = true)
    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Throwable throwable = getRequestException(request);
        HttpStatus statusCode = getStatusWithExceptions(request, throwable);
        return new ResponseEntity<>(getResponseMap(statusCode), statusCode);
    }

    /**
     * 处理错误请求，返回HTML格式的响应。
     *
     * @param request  请求对象
     * @param response 响应对象
     * @return 包含错误信息的ModelAndView对象
     */
    @RequestMapping(produces = "text/html")
    @Operation(hidden = true)
    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        Throwable throwable = getRequestException(request);
        ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
        HttpStatus statusCode = getStatusWithExceptions(request, throwable);
        modelAndView.addAllObjects(getResponseMap(statusCode));
        return modelAndView;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.getFirst().getDefaultMessage();
        return EmptyResponse.error("BadRequest", message);
    }

    @ExceptionHandler(BizException.class)
    public Response handleBizException(BizException e) {
        return EmptyResponse.error(e.getError());
    }

    @ExceptionHandler(NotFoundException.class)
    public Response handleNotFoundException(HttpMessageNotReadableException e) {
        JSONObject jsonObject = JSONObject.parseObject(e.getMessage());
        String message = jsonObject.getString("message");
        return EmptyResponse.error("NotFound", message);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public Response handleViolationException(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        if (!CollectionUtils.isEmpty(violations)) {
            // 可能有多个错误，这里只取第一个
            ConstraintViolation<?> cv = violations.stream().findFirst().orElse(null);
            return EmptyResponse.error(cv.getPropertyPath().toString(), cv.getMessage());
        }
        return responseMap.get(HttpStatus.BAD_REQUEST);
    }

    /**
     * 根据请求获取状态码，并处理异常情况
     *
     * @param request   HttpServletRequest对象
     * @param throwable 捕获的异常
     * @return HttpStatus对象，表示状态码
     */
    private HttpStatus getStatusWithExceptions(HttpServletRequest request, Throwable throwable) {
        HttpStatus statusCode = getStatus(request);
        if (throwable != null) {
            // 使用更灵活的方式来处理异常和状态码的映射
            statusCode = handleSpecificException(throwable);
            // 记录未处理的异常类型，以便于调试和监控
            if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) {
                log.error("Unhandled exception: ", throwable);
            }
        }
        return statusCode;
    }

    /**
     * 处理特定的异常，并返回相应的状态码。
     * 这个方法可以根据需要扩展，以处理更多的异常类型。
     *
     * @param throwable 捕获的异常
     * @return 对应的状态码
     */
    private HttpStatus handleSpecificException(Throwable throwable) {
        if (throwable instanceof UnauthorizedException) {
            return HttpStatus.UNAUTHORIZED;
        } else if (throwable instanceof NotFoundException || throwable instanceof NoResourceFoundException) {
            return HttpStatus.NOT_FOUND;
        } else if (throwable instanceof IllegalArgumentException || throwable instanceof HttpMessageNotReadableException) {
            return HttpStatus.BAD_REQUEST;
        } else if (throwable instanceof IllegalStateException) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        } else if (throwable instanceof ForbiddenException) {
            return HttpStatus.FORBIDDEN;
        } else if (throwable instanceof HttpRequestMethodNotSupportedException) {
            return HttpStatus.METHOD_NOT_ALLOWED;
        }
        // 对于未明确处理的异常类型，返回默认的状态码（例如INTERNAL_SERVER_ERROR）
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }

    /**
     * 根据给定的 HTTP 状态码获取响应映射
     *
     * @param statusCode HTTP 状态码
     * @return 响应映射
     */
    private static Map<String, Object> getResponseMap(HttpStatus statusCode) {
        Response response = Optional.ofNullable(responseMap.get(statusCode)).orElse(EmptyResponse.error("Error", "未知错误"));
        return JSONObject.from(response);
    }

    /**
     * 获取请求异常
     *
     * @param request 请求对象
     * @return 异常对象
     */
    private Throwable getRequestException(HttpServletRequest request) {
        return errorAttributes.getError(new ServletWebRequest(request));
    }
}